//
// Created by mongobaba on 2021/4/19.
//

#ifndef PRINT_PRINT_H
#define PRINT_PRINT_H

#include <iostream>

const char NoSpace{'\0'};

namespace print_impl {
    // use this template to avoid warning
    template<typename T>
    void unused_param(const T &) {}

    template<typename T, typename = void>
    struct is_container_impl : std::false_type {
    };

    template<typename T>
    struct is_container_impl<T, std::void_t<decltype(std::declval<T>().begin())>> : std::true_type {
    };

    template<typename T, size_t N>
    struct is_container_impl<T[N]> : std::integral_constant<bool, !std::is_same<typename std::remove_cv<T>::type, char>::value> {
    };

    template<>
    struct is_container_impl<std::string> : std::false_type {
    };

    template<typename T>
    struct is_container : is_container_impl<
            typename std::remove_cv<
                    typename std::remove_reference<T>::type
            >::type> {
    };

    template<typename T>
    void print_single_element(std::ostream &os, T &&t, std::true_type) {
        if (os) {
            auto it = std::begin(std::forward<T>(t));
            auto end = std::end(std::forward<T>(t));
            os << '[';
            if (it != end) {
                for (;;) {
                    os << *it;
                    if (++it != end) {
                        os << ", ";
                    } else {
                        break;
                    }
                }
            }
            os << ']';
        }
    }

    template<typename T>
    void print_single_element(std::ostream &os, T &&t, std::false_type) {
        os << std::forward<T>(t);
    }

    template<char Space = ' '>
    std::ostream &print_to(std::ostream &os) {
        unused_param(Space);
        return os;
    }

    template<char Space = ' ', typename T, typename... Rest>
    std::ostream &print_to(std::ostream &os, T &&t, Rest &&... rest) {
        print_single_element(os, std::forward<T>(t), is_container<T>{});
        if (sizeof...(rest) > 0 && Space != NoSpace){
            os << Space;
        }
        return print_to<Space>(os, std::forward<Rest>(rest)...);
    }

    template<char Space = ' ', typename... Args>
    std::ostream &print(Args &&... args) {
        return print_to<Space>(std::cout, std::forward<Args>(args)...);
    }

    template<char Space = ' ', typename... Args>
    std::ostream &println_to(std::ostream &os, Args &&... args) {
        print_to<Space>(os, std::forward<Args>(args)...);
        return (os << std::endl);
    }

    template<char Space = ' ', typename... Args>
    std::ostream &println(Args &&... args) {
        return println_to<Space>(std::cout, std::forward<Args>(args)...);
    }
}

using print_impl::print_to;
using print_impl::print;
using print_impl::println_to;
using print_impl::println;

#endif //PRINT_PRINT_H
